package ch.bfh.abch.rems.receiver;

import java.io.IOException;
import java.io.StringReader;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;

import org.apache.log4j.Logger;

import ch.bfh.abch.rems.MulticastMessage;
import ch.bfh.abch.rems.REMSMulticastController;
import ch.bfh.abch.rems.exceptions.UnknownMessageTypeException;
import ch.bfh.abcm.rems.gateway.GatewayMessage;
import ch.bfh.abcm.rems.interfaces.REMSMessageReceiver;
import ch.bfh.abcm.rems.konfiguration.UpdateSensorConfigMessage;
import ch.bfh.abcm.rems.konfiguration.UpdateUserConfigMessage;
import ch.bfh.abcm.rems.tinkerforge.TFNodeAverageMessage;

import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;

public abstract class MulticastReceiver {

    protected static Logger logger = Logger.getLogger("play");

    protected REMSMessageReceiver receiver;
    private MulticastSocket multicastSocket;
    private MulticastReceiverThread receiverThread;

    protected MulticastReceiver(REMSMessageReceiver receiverImplementation, String multicastAdress, int receiverPort) {
        this.receiver = receiverImplementation;
        try {
            this.multicastSocket = new MulticastSocket(receiverPort);
            this.multicastSocket.joinGroup(InetAddress.getByName(multicastAdress));
        } catch (UnknownHostException e) {
            logger.error("UnknownHostException: " + e.getMessage());
        } catch (IOException e) {
            logger.error("IOException: " + e.getMessage());
        }
    }

    public void startReceiving() {
        if (receiverThread != null) {
            if (!receiverThread.isAlive()) {
                logger.debug("Receiversthread is not Alive --> (Re)StartThread");
                receiverThread.start();
            }
        } else {
            logger.debug("ReceiverThread is NULL, create a new Thread");
            receiverThread = new MulticastReceiverThread();
            logger.info("Starting ReceiverThread...");
            receiverThread.start();
        }

    }

    protected abstract void receivedTFNodeAverage(TFNodeAverageMessage tfClusterNodeAverage);

    private synchronized void evaluatedatagramPacket(DatagramPacket datagramPacket) {
        try {
            MulticastMessage multicastMessage = convertDatagrammPacketToMulticastMessage(datagramPacket);
            JsonReader jsonReader = null;
            switch (multicastMessage.getMessageType()) {
            case GATEWAY_MESSAGE:
                logger.info("It's a GatewayMessage!");
                jsonReader = getJsonReader(multicastMessage.getJsonString());
                GatewayMessage gatewayMessage = new Gson().fromJson(jsonReader, GatewayMessage.class);
                logger.debug("Conversion to a GatewayMessageWorked");
                receiver.receivedGateWayMessage(gatewayMessage);
                break;
            case TF_NODE_AVERAGE:
                logger.info("It's a TF Node Average!");
                jsonReader = getJsonReader(multicastMessage.getJsonString());
                TFNodeAverageMessage clusterNodeAverage = new Gson().fromJson(jsonReader, TFNodeAverageMessage.class);
                logger.debug("Conversion to a TFClusterNodeAverage worked");
                receivedTFNodeAverage(clusterNodeAverage);
                break;
            case UPDATE_SENSOR_CONFIG:
                logger.info("It's a Update SensorConfig Message!");
                jsonReader = getJsonReader(multicastMessage.getJsonString());
                UpdateSensorConfigMessage updateSensorConfigMessage = new Gson().fromJson(jsonReader,
                        UpdateSensorConfigMessage.class);
                receiver.receivedUpdateSensorConfigMessage(updateSensorConfigMessage);
                break;
            case UPDATE_USER_CONFIG:
                logger.info("It's a Update UserConfig Message!");
                jsonReader = getJsonReader(multicastMessage.getJsonString());
                UpdateUserConfigMessage updateUserConfigMessage = new Gson().fromJson(jsonReader, UpdateUserConfigMessage.class);
                receiver.receivedUpdateUserConfigMessage(updateUserConfigMessage);
                break;
            default:
                String message = "It's a unknown MessageType: " + multicastMessage.getMessageType();
                logger.error(message, new UnknownMessageTypeException(message));
                break;
            }
        } catch (Exception e) {
            logger.warn("evaluatedatagramPacket failed, dicarded this Multicast: " + e.getMessage());
        }
    }

    private JsonReader getJsonReader(String jsonString) {
        JsonReader reader = new JsonReader(new StringReader(jsonString));
        reader.setLenient(true);
        return reader;
    }

    private synchronized MulticastMessage convertDatagrammPacketToMulticastMessage(DatagramPacket datagramPacket) {
        String data = new String(datagramPacket.getData());
        return new Gson().fromJson(getJsonReader(data), MulticastMessage.class);
    }

    private class MulticastReceiverThread extends Thread {

        @Override
        public void run() {
            logger.debug("Start ReceiverThread");
            byte[] buffer = new byte[REMSMulticastController.DEFAULT_BYTE_ARRAY_LENGTH];
            DatagramPacket datagramPacket = new DatagramPacket(buffer, buffer.length);
            try {
                while (true) {
                    logger.info("Waiting for CNAMulticast...");
                    multicastSocket.receive(datagramPacket);
                    logger.debug("Received MulticastMessage");
                    evaluatedatagramPacket(datagramPacket);
                }
            } catch (IOException e) {
                logger.error("IOException MulticastReceiverThread: " + e.getMessage());
            }
        }

    }
}
